#include "io/vsFileOps.h"
#include <QFileInfo>
#include <QTimer>


vsDataBlockHolder::vsDataBlockHolder(vsDataBlock &block, qint64 offset, qint64 length)
    : _block(block), _offset(offset), _length(length)
{
    _block.useCount++;
}

vsDataBlockHolder::vsDataBlockHolder(const vsDataBlockHolder &other, qint64 offset, qint64 length)
    : _block(other._block)
{
    _offset = (offset == -1) ? other._offset : offset;
    _length = (length == -1) ? other._length : length;

    _block.useCount++;
}

vsDataBlockHolder::~vsDataBlockHolder()
{
    _block.useCount--;
}

const char *vsDataBlockHolder::data() const
{
    return &_block.data.constData()[_offset];
}

//-----------------------------------------------------------------

vsCachedFilePart* vsCachedFile::whichPartHas(qint64 byte)
{
    vsCachedFilePart *result = 0;

    if (byte >= 0 || byte < size)
    {
        int i = 0;

        foreach(const vsCachedFilePart &p, parts)
            if (byte >= p.offset && byte < p.offset + p.length)
            {
                result = &parts[i];
                break;
            }
            else
                i++; // well, yeah, doesn't make much sense...
    }

    return result;
}

bool vsCachedFile::hasValidParts()
{
    bool result = false;

    foreach(const vsCachedFilePart &p, parts)
        if (p.data != 0)
        {
            result = true;
            break;
        }

    return result;
}

vsCachedFile::vsCachedFile(const QString &filePath, qint64 fileSize)
    : path(filePath), size(fileSize)
{
    assert(fileSize > 0); // should never happen

    qint64 partCount = fileSize / c_file_part_size;
    qint64 lastPartS = fileSize % c_file_part_size;

    for (int i = 0; i < partCount; ++i)
        parts.append(vsCachedFilePart(c_file_part_size * i, c_file_part_size));

    if (partCount == 0)
        parts.append(vsCachedFilePart(0, lastPartS));
    else
        if (lastPartS > 0)
            parts.last().length += lastPartS;
}

vsCachedFile::~vsCachedFile()
{
    QList<vsCachedFilePart>::iterator i = parts.begin();

    for (; i != parts.end(); ++i)
    {
        delete i->data;
        i->data = 0;
    }
}

//----------------------------------------------------------------

Q_GLOBAL_STATIC(vsFileStreamer, vsfilestreamer_instance)

vsFileStreamer* vsFileStreamer::instance()
{
    return vsfilestreamer_instance();
}


vsFileStreamer::vsFileStreamer(QObject *parent)
    : QThread(parent), reqID(1), quit(false), wokeUp(false)
{
    start(QThread::LowestPriority);
    QTimer::singleShot(5000, this, SLOT(cleanup()));
}

vsFileStreamer::~vsFileStreamer()
{
    quit = true;
    cond.wakeOne();
    wait();

    while (!fileCache.isEmpty())
    {
        QString k = fileCache.keys().first();
        delete fileCache[k];
        fileCache.remove(k);
    }

    qDebug() << "FileStreamer has left the building!";
}


bool vsFileStreamer::requestRead(int &id, const QString &path, int partIndex)
{
    bool result = false;

    // first let's check if it's in cache ------------------------
    if (fileCache.contains(path))
    {
        vsCachedFile *cf = fileCache[path];

        if (partIndex < cf->parts.length())
        {
            vsCachedFilePart cfp = cf->parts[partIndex];

            if (cfp.data != 0)
            {
                // found, can set id and emit success immediately
                id = reqID++;

                if (reqID >= INT_MAX - 10)
                    reqID = 1;

                emit doneReading(id, vsDataBlockHolder(*cfp.data, 0, cfp.length));
                result = true;
            }
        }
    }

    // not cached, try to read -----------------------------------
    if (!result)
    {
        QFileInfo fi(path);

        if (fi.exists() && fi.isFile() && fi.isReadable() && fi.size() > 0)
        {
            int numberOfParts = qMax(int(fi.size() / c_file_part_size), 1);

            if (partIndex >= 0 && partIndex < numberOfParts)
            {
                id = reqID++;

                if (reqID >= INT_MAX - 10)
                    reqID = 1;

                vsReadRequest rr;
                rr.id     = id;
                rr.path   = path;
                rr.size   = fi.size();
                rr.offset = partIndex * c_file_part_size;
                rr.count  = (partIndex + 1 == numberOfParts)
                        ? rr.size - rr.offset // if that's index of last part, read all bytes after offset
                        : c_file_part_size;
                rr.partIndex = partIndex;

                qDebug() << QString("Will read chunk %1-%2/%3 of %4")
                            .arg(rr.offset).arg(rr.offset + rr.count - 1).arg(rr.size).arg(rr.path);

                acceptedReadRequests[id] = rr;

                QMutexLocker locker(&mutex);
                readRequests << rr;

                if (!wokeUp)
                {
                    wokeUp = true;
                    QMetaObject::invokeMethod(this, "wakeUp", Qt::QueuedConnection);
                }

                result = true;
            }
        }
    }

    return result;
}

bool vsFileStreamer::requestWrite(int &id, const QString &path, qint64 from, qint64 count, const QByteArray &data)
{
    bool result = true;
    id = reqID++;

    if (reqID >= INT_MAX - 10)
        reqID = 1;

    QMutexLocker locker(&mutex);
    writeRequests << vsWriteRequest(path, from, count, data);

    if (!wokeUp)
    {
        wokeUp = true;
        QMetaObject::invokeMethod(this, "wakeUp", Qt::QueuedConnection);
    }

    return result;
}

void vsFileStreamer::run()
{
    do
    {
        {
            // idle if no queries
            QMutexLocker locker(&mutex);

            if (!quit && readRequests.isEmpty() && writeRequests.isEmpty())
                cond.wait(&mutex);
        }

        mutex.lock();
        QList<vsReadRequest> newReadRequests = readRequests;
        readRequests.clear();
        mutex.unlock();

        foreach(const vsReadRequest &rr, newReadRequests)
        {
            bool success = false;
            QFile f(rr.path);

            if (f.open(QFile::ReadOnly))
                if (f.seek(rr.offset))
                {
                    QByteArray data = f.read(rr.count);

                    if (data.length() == rr.count)
                    {
                        success = true;
                        QMetaObject::invokeMethod(this, "internalDoneReading", Qt::QueuedConnection,
                                                  Q_ARG(int,        rr.id),
                                                  Q_ARG(QByteArray, data));
                    }
                }

            f.close();

            if (!success)
                QMetaObject::invokeMethod(this, "internalErrorReading", Qt::QueuedConnection, Q_ARG(int, rr.id));
        }

    } while (!quit);
}

void vsFileStreamer::wakeUp()
{
    QMutexLocker locker(&mutex);
    wokeUp = false;
    cond.wakeOne();
}

void vsFileStreamer::internalDoneReading(int id, QByteArray data)
{
    if (acceptedReadRequests.contains(id))
    {
        vsReadRequest rr = acceptedReadRequests.take(id);

        if (!fileCache.contains(rr.path))
            fileCache[rr.path] = new vsCachedFile(rr.path, rr.size);

        vsCachedFile *cf = fileCache[rr.path];
        vsCachedFilePart &cfp = cf->parts[rr.partIndex];

        if (cfp.data != 0)
            delete cfp.data;

        cfp.data = new vsDataBlock(data);
        emit doneReading(rr.id, vsDataBlockHolder(*cfp.data, 0, data.length()));
        return;
    }

    emit error(id, vsTypes::default_fileError);
}

void vsFileStreamer::internalErrorReading(int id)
{
    qDebug() << "error reading";
    acceptedReadRequests.remove(id);
    emit error(id, vsTypes::default_fileError);
}

void decreaseTimers(QList<vsCachedFilePart> &parts, int time)
{
    if (!parts.empty())
        for (QList<vsCachedFilePart>::iterator i = parts.begin(); i != parts.end(); ++i)
            if (i->data != 0)
            {
                if (i->data->used())
                    i->timer = c_file_part_lifetime;
                else
                {
                    i->timer -= time;

                    if (i->timer <= 0)
                    {
                        delete i->data;
                        i->data = 0;
                    }
                }
            }
}

void vsFileStreamer::cleanup()
{
    if (!fileCache.isEmpty())
    {
        QStringList keysToDelete;
        QMap<QString, vsCachedFile*>::iterator i = fileCache.begin();

        for (; i != fileCache.end(); ++i)
        {
            vsCachedFile *cf = i.value();

            if (cf == 0 || !cf->hasValidParts())
                keysToDelete << i.key();

            if (cf != 0)
                decreaseTimers(cf->parts, 5000);
        }

        foreach (const QString &key, keysToDelete)
        {
            delete fileCache[key];
            fileCache.remove(key);
        }
    }

    QTimer::singleShot(5000, this, SLOT(cleanup()));
}
